package edu.hawaii.halealohacli.command;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.source.jaxb.Source;

/**
 * This command defines [date] as the "baseline" day for [tower | lounge].  
 * [date] is an optional argument in YYYY-MM-DD format and defaults to yesterday.
 * When this command is executed, the system should obtain and save the amount of 
 * energy used during each of the 24 hours of that day for the given tower or lounge.  
 * These 24 values define the baseline power for that tower or lounge for that one 
 * hour time interval.  For example, if lounge Ilima-A used 100 kWh of energy during 
 * the hour 6am-7am, then the baseline power during the interval 6am - 7am for 
 * Ilima-A is 100 kW.
 * 
 * @author Bryson Y. Hori
 */

public class SetBaseline implements Command {
  
  private WattDepotClient client;
  private String name;
  private String usage;
  private String description;
  private SourceData dataModule;
  private String nameFromUser;
  private String dateFromUser;
  private String errorMsg;
  private List<String> pTimes = new ArrayList<String>();

  /**
   * Creates an instance of this command with the client set to null by default, requiring
   * initialization.
   */
  public SetBaseline() {
    this.name = "set-baseline";
    this.usage = "set-baseline [tower | lounge] [date]";
    this.description =
        "Returns a baseline power for a given [tower | lounge] for one hour time interval"
            + " [date] is an optional argument in YYYY-MM-DD format and defaults to yesterday.";
    this.errorMsg = "The date %s is invalid " + "(Enter \"help\" to see command usage)";
  }
  /**
   * Initializes this command with a healthy WattDepot client.
   * 
   * @param client A healthy WattDepot client used to initialize the command
   * @param args Arguments required by the command for execution
   * @return Whether or not the arguments passed was accepted by the command initialization
   */
  @Override
  public boolean initializeCommand(WattDepotClient client, String[] args) {
    this.client = client;
    if (args.length < 2 || args.length > 3) {
      System.out.format("The arguments are invalid " + "(Enter \"help\" to see command usage)");
      return false;
    }
    //This is the required argument of the towers name.
    this.nameFromUser = args[1];
    if (args.length == 3) {
      //This means that the user entered the optional argument of date.
      this.dateFromUser = args[2];
    }
    else {
      //Need to set the date to default of yesterday
      Calendar defaultTime = GregorianCalendar.getInstance();
      defaultTime.add(Calendar.HOUR, -48);
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
      this.dateFromUser = sdf.format(defaultTime.getTime());
    }
    return true;
  }

  /**
   * Returns the name of this command.
   * 
   * @return the name of this command
   */
  @Override
  public String getName() {
    return this.name;
  }

  /**
   * Returns the usage syntax of this command.
   * 
   * @return the usage syntax of this command
   */
  @Override
  public String getUsage() {
    return this.usage;
  }

  /**
   * Returns the description of this command.
   * 
   * @return a description of this command
   */
  @Override
  public String getDescription() {
    return this.description;
  }
  
  /**
   * Getter. Returns information needed and to be used for other methods.
   * Must call initializeCommand before one can call this method.
   * @return List<Double> dataModula.getBaseline() 
   */
  public List<Double> getBaselineValueForJohn() {
    if (dataModule.getBaseline() == null) {
      initializeData(this.nameFromUser, this.dateFromUser);
    }
    return this.dataModule.getBaseline();
  }
  
  /**
   * Getter. Returns info needed and to be used for other methods.
   * Must call initializeCommand before on can call this method.
   * @return List<String> pTimes 
   */
  public List<String> getTimeValueForJohn() {
    if (pTimes == null) {
      initializeData(this.nameFromUser, this.dateFromUser);
    }
    return this.pTimes;
  }

  /**
   * Initialize the SouceData with the energy data obtained from the client.
   * 
   * @param name The name of the tower or lounge to get data from
   * @param date The date from which the energy data should be from
   * @return Whether or not the data was successfully initialized
   */
  public boolean initializeData(String name, String date) {
    try {
      Source source = client.getSource(name);
      Calendar startTime = getStartTime(date);
      if (startTime == null) {
        System.out.format(errorMsg, date);
        return false;
      }
      Calendar endTime = (Calendar) startTime.clone();
      dataModule = new SourceData(source.getName());
      for (int i = 0; i < 24; i++) {
        endTime.add(Calendar.HOUR, 1);
        dataModule.setBaseline(client.getEnergyConsumed(source.getName(),
            toXMLGregorianCalendar(startTime), toXMLGregorianCalendar(endTime), 15));
        String temp = "time: " + startTime.get(Calendar.HOUR) + "-" + endTime.get(Calendar.HOUR);
        pTimes.add(temp);
        startTime.add(Calendar.HOUR, 1);
      }
      return true;
    }
    catch (NotAuthorizedException e) {
      System.out.format("You are not authorized to view %s", name);
      return false;
    }
    catch (ResourceNotFoundException e) {
      System.out.format("The tower or lounge %s is invalid "
          + "(Enter \"help\" to see available sources", name);
      return false;
    }
    catch (BadXmlException e) {
      System.out.format("Bad XML: %s", e.getMessage());
      return false;
    }
    catch (MiscClientException e) {
      System.out.format("Unknown error has occurred: %s", e.getMessage());
      return false;
    }
  }

  /**
   * Try to convert a String representation of a date into a Calendar representation.
   * 
   * @param date String representation of a date
   * @return A Calendar representation for the input date or NULL if the date was invalid
   */
  protected Calendar getStartTime(String date) {
    Calendar inputDate = GregorianCalendar.getInstance();
    Calendar today = GregorianCalendar.getInstance();
    Date convertedDate = convertDate(date);
    if (convertedDate == null) {
      return null;
    }
    inputDate.setTime(convertedDate);
    if (inputDate.compareTo(today) <= 0) {
      return inputDate;
    }
    else {
      return null;
    }
  }

  /**
   * Try to convert a String representation into a Date object.
   * 
   * @param date String representation of a date
   * @return A Date object for the input date or NULL if the conversion was unsuccessful
   */
  protected Date convertDate(String date) {
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    try {
      Date parsedDate = formatter.parse(date);
      if (!formatter.format(parsedDate).equals(date)) {
        System.out.format(errorMsg, date);
        return null;
      }
      return parsedDate;

    }
    catch (Exception e) {
      System.out.format(errorMsg, date);
      return null;
    }
  }

  /**
   * Prints the results of executing this command.
   */
  @Override
  public void printResults() {
    if (!initializeData(this.nameFromUser, this.dateFromUser)) {
      return;
    }
    System.out.format("%s's baseline for %s is:\n", dataModule.getName(), this.dateFromUser);
    Iterator<Double> it1 = dataModule.getBaseline().iterator();
    Iterator<String> it2 = pTimes.iterator();
    while (it1.hasNext() && it2.hasNext()) {
      System.out.format("%.1f kW %s\n", it1.next(), it2.next());
    }
  }

  /**
   * Converts a calendar object into a XMLGregorianCalendar object.
   * 
   * @param date The Calendar object to be converted
   * @return The converted XMLGregorianCalendar object
   */
  protected XMLGregorianCalendar toXMLGregorianCalendar(Calendar date) {
    GregorianCalendar calendar = (GregorianCalendar) date;
    try {
      DatatypeFactory converter = DatatypeFactory.newInstance();
      return converter.newXMLGregorianCalendar(calendar);
    }
    catch (DatatypeConfigurationException e) {
      System.out.format("There was a problem in the date conversion: " + e.getMessage());
      return null;
    }
  }
}
